/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.workflow.simple.alfresco.conversion;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.workflow.simple.alfresco.conversion.exception.AlfrescoSimpleWorkflowException;
import org.activiti.workflow.simple.alfresco.conversion.form.AlfrescoFormCreator;
import org.activiti.workflow.simple.alfresco.conversion.script.PropertyReference;
import org.activiti.workflow.simple.alfresco.conversion.script.ScriptTaskListenerBuilder;
import org.activiti.workflow.simple.alfresco.model.M2Model;
import org.activiti.workflow.simple.alfresco.model.M2Namespace;
import org.activiti.workflow.simple.alfresco.model.M2Type;
import org.activiti.workflow.simple.alfresco.model.config.Configuration;
import org.activiti.workflow.simple.alfresco.model.config.Form;
import org.activiti.workflow.simple.alfresco.model.config.Module;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.step.HumanStepDefinitionConverter;
import org.activiti.workflow.simple.converter.step.StepDefinitionConverter;
import org.activiti.workflow.simple.definition.HumanStepAssignment.HumanStepAssignmentType;
import org.activiti.workflow.simple.definition.HumanStepDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.exception.SimpleWorkflowException;

/**
 * A {@link StepDefinitionConverter} which adds a content-model and a form-config to the conversion
 * based on the {@link HumanStepDefinition} that is converted.
 *
 * @author Frederik Heremans
 */
public class AlfrescoHumanStepDefinitionConverter extends HumanStepDefinitionConverter {

    private static final long serialVersionUID = 1L;

    private AlfrescoFormCreator formCreator;

    public AlfrescoHumanStepDefinitionConverter() {
        formCreator = new AlfrescoFormCreator();
    }

    @Override
    public Class<? extends StepDefinition> getHandledClass() {
        return HumanStepDefinition.class;
    }

    @Override
    public UserTask convertStepDefinition(StepDefinition stepDefinition, WorkflowDefinitionConversion conversion) {
        HumanStepDefinition humanStep = (HumanStepDefinition) stepDefinition;
        validate(humanStep);

        M2Model model = AlfrescoConversionUtil.getContentModel(conversion);
        M2Namespace modelNamespace = model.getNamespaces().get(0);

        // Let superclass handle BPMN-specific conversion
        UserTask userTask = super.convertStepDefinition(stepDefinition, conversion);

        // Clear form-properties in the BPMN file, as we use custom form-mapping in Alfresco
        userTask.getFormProperties().clear();

        userTask.setName(humanStep.getName() != null ? humanStep.getName() : humanStep.getId());

        // Create the content model for the task
        M2Type type = new M2Type();
        model.getTypes().add(type);
        type.setName(AlfrescoConversionUtil.getQualifiedName(modelNamespace.getPrefix(),
                humanStep.getId()));
        type.setParentName(AlfrescoConversionConstants.DEFAULT_BASE_FORM_TYPE);

        // Update task-key on the task itself
        userTask.setFormKey(type.getName());

        // Create a form-config for the task
        Module shareModule = AlfrescoConversionUtil.getExtension(conversion).getModules().get(0);
        Configuration configuration = shareModule.addConfiguration(AlfrescoConversionConstants.EVALUATOR_TASK_TYPE
                , type.getName());
        Form formConfig = configuration.createForm();

        // Populate model and form based on FormDefinition
        formCreator.createForm(type, formConfig, humanStep.getForm(), conversion);

        // Set up property sharing using task-listeners
        addPropertySharing(humanStep, conversion, userTask);

        // Special handling for assignee that reference form-properties, before BPMN
        // is created
        if (humanStep.getAssignmentType() == HumanStepAssignmentType.USER) {
            String assignee = humanStep.getAssignment().getAssignee();

            if (assignee != null && PropertyReference.isPropertyReference(assignee)) {
                PropertyReference reference = PropertyReference.createReference(assignee);
                AlfrescoConversionUtil.getPropertyReferences(conversion).add(reference);
                userTask.setAssignee(reference.getUsernameReferenceExpression(modelNamespace.getPrefix()));
            }
        } else if (humanStep.getAssignmentType() == HumanStepAssignmentType.USERS) {
            if (humanStep.getAssignment().getCandidateUsers() != null) {
                userTask.setCandidateUsers(resolveUserPropertyReferences(humanStep.getAssignment().getCandidateUsers(), modelNamespace.getPrefix(), conversion));
            }
        } else if (humanStep.getAssignmentType() == HumanStepAssignmentType.GROUPS) {
            if (humanStep.getAssignment().getCandidateGroups() != null) {
                userTask.setCandidateGroups(resolveGroupPropertyReferences(humanStep.getAssignment().getCandidateGroups(), modelNamespace.getPrefix(), conversion));
            }
        }

        return userTask;
    }

    protected UserTask locateUserTask(WorkflowDefinitionConversion conversion) {
        List<FlowElement> elements = (List<FlowElement>) conversion.getProcess().getFlowElements();

        for (int i = elements.size() - 1; i >= 0; i--) {
            if (elements.get(i) instanceof UserTask) {
                return (UserTask) elements.get(i);
            }
        }
        throw new SimpleWorkflowException("No usertask found in conversion");
    }

    protected void addPropertySharing(HumanStepDefinition humanStep, WorkflowDefinitionConversion conversion, UserTask userTask) {
        PropertySharing sharing = AlfrescoConversionUtil.getPropertySharing(conversion, userTask.getId());

        // Add default incoming properties (due-date and priority)
        // TODO: make optional?
        ScriptTaskListenerBuilder createEventBuilder = AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(),
                AlfrescoConversionConstants.TASK_LISTENER_EVENT_CREATE);

        createEventBuilder.addDueDateInheritance();
        createEventBuilder.addPriorityInheritance();

        // Add create-listener in case incoming properties are present
        if (sharing.hasIncomingProperties()) {
            createEventBuilder = AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(),
                    AlfrescoConversionConstants.TASK_LISTENER_EVENT_CREATE);
            for (Entry<String, String> entry : sharing.getIncomingProperties().entrySet()) {
                createEventBuilder.addIncomingProperty(entry.getKey(), entry.getValue());
            }
        }

        // Add complete-listener in case incoming properties are present
        if (sharing.hasOutgoingProperties()) {
            ScriptTaskListenerBuilder completeEventBuilder = AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(),
                    AlfrescoConversionConstants.TASK_LISTENER_EVENT_COMPLETE);
            for (Entry<String, String> entry : sharing.getOutgoingProperties().entrySet()) {
                completeEventBuilder.addOutgoingProperty(entry.getKey(), entry.getValue());
            }
        }
    }

    protected List<String> resolveUserPropertyReferences(List<String> list, String namespacePrefix, WorkflowDefinitionConversion conversion) {
        if (list != null) {
            List<String> result = new ArrayList<String>();
            PropertyReference propertyReference = null;
            for (String string : list) {
                if (PropertyReference.isPropertyReference(string)) {
                    propertyReference = PropertyReference.createReference(string);
                    result.add(propertyReference.getUsernameReferenceExpression(namespacePrefix));

                    // Add reference to be validated
                    AlfrescoConversionUtil.getPropertyReferences(conversion).add(propertyReference);
                } else {
                    result.add(string);
                }
            }
            return result;
        }
        return null;
    }

    protected List<String> resolveGroupPropertyReferences(List<String> list, String namespacePrefix, WorkflowDefinitionConversion conversion) {
        if (list != null) {
            List<String> result = new ArrayList<String>();
            PropertyReference propertyReference = null;
            for (String string : list) {
                if (PropertyReference.isPropertyReference(string)) {
                    propertyReference = PropertyReference.createReference(string);
                    result.add(propertyReference.getGroupReferenceExpression(namespacePrefix));

                    // Add reference to be validated
                    AlfrescoConversionUtil.getPropertyReferences(conversion).add(propertyReference);
                } else {
                    result.add(string);
                }
            }
            return result;
        }
        return null;
    }

    @Override
    protected String getInitiatorExpression() {
        // Use the correct assignee expression if the initiator is used for assignment
        return AlfrescoConversionConstants.INITIATOR_ASSIGNEE_EXPRESSION;
    }

    @Override
    protected String getInitiatorVariable() {
        // Variable used to store the assignee
        return AlfrescoConversionConstants.INITIATOR_VARIABLE;
    }


    protected void validate(HumanStepDefinition stepDefinition) {
        if (stepDefinition.getId() == null) {
            if (stepDefinition.getName() == null && !stepDefinition.getName().isEmpty()) {
                throw new AlfrescoSimpleWorkflowException("Name or id of a human step is required.");
            }
            stepDefinition.setId(AlfrescoConversionUtil.getValidIdString(stepDefinition.getName()));
        }
    }

}
